use std::{
    thread::sleep,
    time::{Duration, SystemTime},
};

use cgmath::{vec2, vec4};
use scene::Renderer;
use tetris_core::{
    game::{self, Game, Ret},
    input::{Input, InputMap},
    math::Rect,
    tetris::Model,
};

use winit::{
    dpi::PhysicalSize,
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    keyboard::{KeyCode, PhysicalKey},
    window::WindowBuilder,
};

pub mod math;
pub mod scene;

fn main() {
    let event_loop = EventLoop::new().unwrap();
    let window = WindowBuilder::new()
        .with_title("tetris")
        .with_inner_size(PhysicalSize {
            width: 800,
            height: 800,
        })
        .build(&event_loop)
        .unwrap();

    let t = Model::new(1, [vec2(-1, 0), vec2(0, 0), vec2(1, 0), vec2(0, 1)]);
    let o = Model::new(2, [vec2(1, 0), vec2(0, 0), vec2(0, 1), vec2(1, 1)]);
    let l = Model::new(3, [vec2(0, 0), vec2(1, 0), vec2(0, 2), vec2(0, 1)]);
    let j = Model::new(4, [vec2(0, 0), vec2(-1, 0), vec2(0, 1), vec2(0, 2)]);
    let i = Model::new(5, [vec2(0, 0), vec2(0, -1), vec2(0, 1), vec2(0, 2)]);

    let models = vec![t, o, l, j, i];

    let mut game = Game::new(&models, (20, 40));
    let mut inputs = Input::new();

    let mut r = Renderer::new(&window);

    r.board.set_position(vec2(0.5, 0.5));
    r.tetris.reset(game.tetris());

    r.batch.add_rect(
        Rect::new(25.0, 35.0, 21.0, 31.0),
        vec4(0.01, 0.01, 0.01, 1.0),
    );

    r.next.reset_by_model(game.next_model());
    r.next.set_position(vec2(30.0, 25.0));

    let mut prev_time = SystemTime::now();
    event_loop.set_control_flow(ControlFlow::Poll);
    event_loop
        .run(|event, target| match event {
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => target.exit(),
            Event::AboutToWait => {
                window.request_redraw();
            }
            Event::WindowEvent {
                event: WindowEvent::KeyboardInput { event, .. },
                ..
            } => {
                if event.state.is_pressed() {
                    match event.physical_key {
                        PhysicalKey::Code(KeyCode::Escape) => inputs.push_event(InputMap::Exit),
                        PhysicalKey::Code(KeyCode::ArrowUp) => inputs.push_event(InputMap::Rotate),
                        PhysicalKey::Code(KeyCode::ArrowLeft) => {
                            inputs.push_event(InputMap::MoveLeft)
                        }
                        PhysicalKey::Code(KeyCode::ArrowRight) => {
                            inputs.push_event(InputMap::MoveRight)
                        }
                        PhysicalKey::Code(KeyCode::ArrowDown) => {
                            inputs.push_event(InputMap::MoveDown)
                        }
                        _ => (),
                    }
                }
            }
            Event::WindowEvent {
                event: WindowEvent::Resized(size),
                ..
            } => {
                r.resize(size.width, size.height);
            }
            Event::WindowEvent {
                event: WindowEvent::RedrawRequested,
                ..
            } => {
                let cur_time = SystemTime::now();
                let delta = cur_time.duration_since(prev_time).unwrap();

                match game.tick(&mut inputs, delta.as_millis() as usize) {
                    Ret::Continue(ge) => {
                        r.board.sync(game.board());
                        if let game::Event::Updated = ge {
                            r.tetris.reset(game.tetris());
                            r.next.reset_by_model(game.next_model());
                        } else {
                            r.tetris.sync(game.tetris());
                        }
                    }
                    Ret::Exit => target.exit(),
                }
                prev_time = cur_time;
                r.render();

                sleep(Duration::from_millis(50))
            }
            _ => (),
        })
        .unwrap();
}
